<?php  if ( ! defined('__CORE_PATH__')) exit('No direct script access allowed');
/* SVN FILE: $Id: app_controller.php 25 2008-08-29 21:58:28Z namaless $ */
/**
 * Helper HTML.
 *
 * @author			Pereira Pulido Nuno Ricardo | Namaless | namaless@gmail.com
 * @copyright		Copyright 1981-2008, Namaless.
 * @link			http://www.namaless.com Namaless Blog
 * @version			1.0
 * @version			$Revision: 25 $
 * @modifiedby		$LastChangedBy: namaless $
 * @lastmodified	$Date: 2008-08-29 23:58:28 +0200 (ven, 29 ago 2008) $
 * @license			http://www.opensource.org/licenses/gpl-license.php GNU Public License
 */

/**
* Classe Principale.
*/
class Session
{
	var $now;
	var $encryption		= TRUE;
	var $use_database	= FALSE;
	var $session_table	= FALSE;
	var $sess_length	= 7200;
	var $sess_cookie	= 'evophp_session';
	var $userdata		= array();
	var $gc_probability	= 5;
	var $flashdata_key 	= 'flash';
	var $time_to_update	= 300;

	/**
	 * Singleton Pattern.
	 *
	 * @var object
	 **/
	private static $instance = NULL;

	// --------------------------------------------------------------------

	/**
	 * Implemento il Singleton.
	 *
	 * @return	object
	 */
	public static function get_instance()
	{
		if ( self::$instance == NULL )
		{   
			$c = __CLASS__;
		    self::$instance =& new $c;
		}
		
		return self::$instance;
	}

	// --------------------------------------------------------------------

	private function __construct(){}
	
	// --------------------------------------------------------------------

	private function __clone(){}
	
	// --------------------------------------------------------------------

	public function __destruct(){}
	
	// --------------------------------------------------------------------

	/**
	 * Implemento il Factory.
	 */
	public static function factory()
	{
		// Inserisco un marcatore.
		Benchmark::mark('session_factory_start');
		
		// Avvio la sessione.
		self::sess_run();
		
		// Inserisco un marcatore.
		Benchmark::mark('session_factory_end');
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Run the session routines
	 *
	 * @access	public
	 * @return	void
	 */		
	public static function sess_run()
	{
		// Creo l'oggetto.
		$session = self::get_instance();
		
		/*
		 *  Set the "now" time
		 *
		 * It can either set to GMT or time(). The pref
		 * is set in the config file.  If the developer
		 * is doing any sort of time localization they
		 * might want to set the session time to GMT so
		 * they can offset the "last_activity" time
		 * based on each user's locale.
		 *
		 */

		if ( is_numeric(App::setting('Enviroments.sess_time_to_update')) )
		{
			$session->time_to_update = App::setting('Enviroments.sess_time_to_update');
		}

		if (strtolower(App::setting('Enviroments.time_reference')) == 'gmt')
		{
			$now = time();
			$session->now = mktime(gmdate("H", $now), gmdate("i", $now), gmdate("s", $now), gmdate("m", $now), gmdate("d", $now), gmdate("Y", $now));
	
			if (strlen($session->now) < 10)
			{
				$session->now = time();
			}
		}
		else
		{
			$session->now = time();
		}
		
		/*
		 *  Set the session length
		 *
		 * If the session expiration is set to zero in
		 * the config file we'll set the expiration
		 * two years from now.
		 *
		 */
		$expiration = App::setting('Enviroments.sess_expiration');
		
		if ( is_numeric($expiration) )
		{
			if ($expiration > 0)
			{
				$session->sess_length = App::setting('Enviroments.sess_expiration');
			}
			else
			{
				$session->sess_length = (60*60*24*365*2);
			}
		}
		
		// Do we need encryption?
		$session->encryption = App::setting('Enviroments.sess_encrypt_cookie');

		// Are we using a database?
		if ( App::setting('Enviroments.sess_use_database') === TRUE AND App::setting('Enviroments.sess_table_name') != '' )
		{
			$session->use_database = TRUE;
			$session->session_table = App::setting('Enviroments.sess_table_name');
		}
		
		// Set the cookie name
		if ( App::setting('Enviroments.sess_cookie_name') != FALSE )
		{
			$session->sess_cookie = App::setting('Enviroments.cookie_prefix') . App::setting('Enviroments.sess_cookie_name');
		}

		/*
		 *  Fetch the current session
		 *
		 * If a session doesn't exist we'll create
		 * a new one.  If it does, we'll update it.
		 *
		 */
		if ( ! self::sess_read() )
		{
			self::sess_create();
		}
		else
		{	
			// We only update the session every five minutes
			if ( ($session->userdata['last_activity'] + $session->time_to_update) < $session->now )
			{
				self::sess_update();
			}
		}
		
		// Delete expired sessions if necessary
		if ( $session->use_database === TRUE )
		{		
			self::sess_gc();
		}

		// Delete 'old' flashdata (from last request)
       	$session->_flashdata_sweep();
        
        // Mark all new flashdata as old (data will be deleted before next request)
       	$session->_flashdata_mark();
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Fetch the current session data if it exists
	 *
	 * @access	public
	 * @return	void
	 */
	public static function sess_read()
	{
		// Creo l'oggetto.
		$session = self::get_instance();
		
		// Fetch the cookie
		$session_data = Input::cookie($session->sess_cookie);
		
		if ( $session_data === FALSE )
		{
			return FALSE;
		}

		// Decrypt and unserialize the data
		if ( $session->encryption == TRUE )
		{
			//Encrypt::decode($session_data);
		}
		else
		{	
			// encryption was not used, so we need to check the md5 hash
			$hash = substr($session_data, strlen($session_data)-32); // get last 32 chars
			$session_data = substr($session_data, 0, strlen($session_data)-32);

			// Does the md5 hash match?  This is to prevent manipulation of session data
			// in userspace
			if ( $hash !==  md5(App::setting('Enviroments.encryption_key')) )
			{
				//log_message('error', 'The session cookie data did not match what was expected. This could be a possible hacking attempt.');
				self::sess_destroy();
				return FALSE;
			}
		}
		
		$session_data = @unserialize(self::strip_slashes($session_data));
		
		if ( ! is_array($session_data) OR ! isset($session_data['last_activity']) )
		{
			//log_message('error', 'The session cookie data did not contain a valid array. This could be a possible hacking attempt.');
			return FALSE;
		}
		
		// Is the session current?
		if ( ($session_data['last_activity'] + $session->sess_length) < $session->now )
		{
			self::sess_destroy();
			return FALSE;
		}

		// Does the IP Match?
		if ( App::setting('Enviroments.sess_match_ip') == TRUE AND $session_data['ip_address'] != Input::ip_address() )
		{
			$this->sess_destroy();
			return FALSE;
		}
		
		// Does the User Agent Match?
		if ( App::setting('Enviroments.sess_match_useragent') == TRUE AND trim($session_data['user_agent']) != trim(substr(Input::user_agent(), 0, 50)) )
		{
			self::sess_destroy();
			return FALSE;
		}
		
		// Is there a corresponding session in the DB?
		if ( $session->use_database === TRUE )
		{
			// Creo l'oggetto.
			$database = Database::get_instance();
			
			$database->where('session_id', $session_data['session_id']);
					
			if ( App::setting('Enviroments.sess_match_ip') == TRUE )
			{
				$database->where('ip_address', $session_data['ip_address']);
			}

			if ( App::setting('Enviroments.sess_match_useragent') == TRUE )
			{
				$database->where('user_agent', $session_data['user_agent']);
			}
			
			$query = $database->get($this->session_table);

			if ( $query->num_rows() == 0 )
			{
				self::sess_destroy();
				return FALSE;
			}
			else
			{
				$row = $query->row();
				if ( ($row->last_activity + $session->sess_length) < $session->now )
				{
					$database->where('session_id', $session_data['session_id']);
					$database->delete($session->session_table);
					self::sess_destroy();
					return FALSE;
				}
			}
		}
	
		// Session is valid!
		$session->userdata = $session_data;
		unset($session_data);
		
		return TRUE;
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Write the session cookie
	 *
	 * @access	public
	 * @return	void
	 */
	public static function sess_write()
	{
		// Creo l'oggetto.
		$session = self::get_instance();
							
		$cookie_data = serialize($session->userdata);
		
		if ( $session->encryption == TRUE )
		{
			//$cookie_data = Encrypt::encode($cookie_data);
		}
		else
		{
			// if encryption is not used, we provide an md5 hash to prevent userside tampering
			$cookie_data = $cookie_data . md5($cookie_data . App::setting('Enviroments.encryption_key'));
		}

		setcookie(
					$session->sess_cookie,
					$cookie_data,
					$session->sess_length + time(),
					App::setting('Enviroments.cookie_path'),
					App::setting('Enviroments.cookie_domain'),
					0
				);
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Create a new session
	 *
	 * @access	public
	 * @return	void
	 */
	public static function sess_create()
	{
		// Creo l'oggetto.
		$session = self::get_instance();
		
		$sessid = '';
		while ( strlen($sessid) < 32 )
		{
			$sessid .= mt_rand(0, mt_getrandmax());
		}
	
		$session->userdata = array(
							'session_id' 	=> md5(uniqid($sessid, TRUE)),
							'ip_address' 	=> Input::ip_address(),
							'user_agent' 	=> substr(Input::user_agent(), 0, 50),
							'last_activity'	=> $session->now
							);
		
		
		// Save the session in the DB if needed
		if ( $session->use_database === TRUE )
		{
			$database = Database::get_instance();
			$database->query($database->insert_string($session->session_table, $session->userdata));
		}
			
		// Write the cookie
		self::sess_write();
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Update an existing session
	 *
	 * @access	public
	 * @return	void
	 */
	public static function sess_update()
	{
		// Creo l'oggetto.
		$session = self::get_instance();
		
		// Save the old session id so we know which record to 
		// update in the database if we need it
		$old_sessid = $session->userdata['session_id'];
		$new_sessid = '';
		while ( strlen($new_sessid) < 32 )
		{
			$new_sessid .= mt_rand(0, mt_getrandmax());
		}
		$new_sessid = md5(uniqid($new_sessid, TRUE));
		
        // Update the session data in the session data array
		$session->userdata['session_id'] = $new_sessid;
		$session->userdata['last_activity'] = $session->now;
		
		// Update the session in the DB if needed
		if ( $session->use_database === TRUE )
		{
			$database = Database::get_instance();
			$database->query($database->update_string($session->session_table, array('last_activity' => $session->now, 'session_id' => $new_sessid), array('session_id' => $old_sessid)));
		}
		
		// Write the cookie
		self::sess_write();
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Destroy the current session
	 *
	 * @access	public
	 * @return	void
	 */
	public static function sess_destroy()
	{
		// Creo l'oggetto.
		$session = self::get_instance();

		setcookie(
					$session->sess_cookie,
					addslashes(serialize(array())),
					($session->now - 31500000),
					App::setting('Enviroments.cookie_path'),
					App::setting('Enviroments.cookie_domain'),
					0
				);
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Garbage collection
	 *
	 * This deletes expired session rows from database
	 * if the probability percentage is met
	 *
	 * @access	public
	 * @return	void
	 */
	public static function sess_gc()
	{
		// Creo l'oggetto.
		$session = self::get_instance();
		
		srand(time());
		if ( (rand() % 100) < $session->gc_probability )
		{
			$expire = $this->now - $session->sess_length;
			
			$database = Database::get_instance();
			$database->where("last_activity < {$expire}");
			$database->delete($session->session_table);

			//log_message('debug', 'Session garbage collection performed.');
		}
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Fetch a specific item from the session array
	 *
	 * @access	public
	 * @param	string
	 * @return	string
	 */		
	public static function userdata($item)
	{
		$session = self::get_instance();
		return ( ! isset($session->userdata[$item]) ) ? FALSE : $session->userdata[$item];
	}

	// --------------------------------------------------------------------
	
	/**
	 * Fetch all session data
	 *
	 * @access	public
	 * @return	mixed
	 */	
	public static function all_userdata()
	{
		$session = self::get_instance();
        return ( ! isset($session->userdata) ) ? FALSE : $session->userdata;
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Add or change data in the "userdata" array
	 *
	 * @access	public
	 * @param	mixed
	 * @param	string
	 * @return	void
	 */		
	public static function set_userdata($newdata = array(), $newval = '')
	{
		if ( is_string($newdata) )
		{
			$newdata = array($newdata => $newval);
		}
	
		if ( count($newdata) > 0 )
		{
			foreach ( $newdata as $key => $val )
			{
				self::get_instance()->userdata[$key] = $val;
			}
		}

		self::sess_write();
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Delete a session variable from the "userdata" array
	 *
	 * @access	array
	 * @return	void
	 */		
	public static function unset_userdata($newdata = array())
	{
		if ( is_string($newdata) )
		{
			$newdata = array($newdata => '');
		}
	
		if ( count($newdata) > 0 )
		{
			foreach ( $newdata as $key => $val )
			{
				unset(self::get_instance()->userdata[$key]);
			}
		}
	
		self::sess_write();
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Strip slashes
	 *
	 * @access	public
	 * @param	mixed
	 * @return	mixed
	 */
	public static function strip_slashes($vals)
	{
		if ( is_array($vals) )
	 	{	
	 		foreach ( $vals as $key=>$val )
	 		{
	 			$vals[$key] = self::strip_slashes($val);
	 		}
	 	}
	 	else
	 	{
	 		$vals = stripslashes($vals);
	 	}
	 	
	 	return $vals;
	}

	
    // ------------------------------------------------------------------------

    /**
	 * Add or change flashdata, only available
	 * until the next request
	 *
	 * @access	public
	 * @param	mixed
	 * @param	string
	 * @return	void
	 */
	public static function set_flashdata($newdata = array(), $newval = '')
    {
    	// Creo l'oggetto.
    	$session = self::get_instance();
    	
        if ( is_string($newdata) )
        {
            $newdata = array($newdata => $newval);
        }
        
        if ( count($newdata) > 0 )
        {
            foreach ( $newdata as $key => $val )
            {
                $flashdata_key = $session->flashdata_key . ':new:' . $key;
                self::set_userdata($flashdata_key, $val);
            }
        }
    } 
	
    // ------------------------------------------------------------------------

    /**
     * Keeps existing flashdata available to next request.
	 *
	 * @access	public
	 * @param	string
	 * @return	void
     */
    public static function keep_flashdata($key)
    {
		// 'old' flashdata gets removed.  Here we mark all 
		// flashdata as 'new' to preserve it from _flashdata_sweep()
		// Note the function will return FALSE if the $key 
		// provided cannot be found
        $old_flashdata_key = self::get_instance()->flashdata_key.':old:'.$key;
        $value = self::userdata($old_flashdata_key);

        $new_flashdata_key = self::get_instance()->flashdata_key.':new:'.$key;
        self::set_userdata($new_flashdata_key, $value);
    }
	
    // ------------------------------------------------------------------------

	/**
	 * Fetch a specific flashdata item from the session array
	 *
	 * @access	public
	 * @param	string
	 * @return	string
	 */	
    public static function flashdata($key)
    {    	
        $flashdata_key = self::get_instance()->flashdata_key.':old:'.$key;
        return self::userdata($flashdata_key);
    }

    // ------------------------------------------------------------------------

    /**
     * Identifies flashdata as 'old' for removal
	 * when _flashdata_sweep() runs.
	 *
	 * @access	private
	 * @return	void
     */
    function _flashdata_mark()
    {
		$userdata = $this->all_userdata();
        foreach ( $userdata as $name => $value )
        {
            $parts = explode(':new:', $name);
            if ( is_array($parts) AND count($parts) === 2 )
            {
                $new_name = $this->flashdata_key.':old:'.$parts[1];
                $this->set_userdata($new_name, $value);
                $this->unset_userdata($name);
            }
        }
    }

    // ------------------------------------------------------------------------

    /**
     * Removes all flashdata marked as 'old'
	 *
	 * @access	private
	 * @return	void
     */

    function _flashdata_sweep()
    {
		$userdata = $this->all_userdata();
        foreach ( $userdata as $key => $value )
        {
            if ( strpos($key, ':old:') )
            {
                $this->unset_userdata($key);
            }
        }

    }
	
}

